
package com.media.module.view;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import com.media.module.R;
import com.media.module.fragment.SettingFragment;
import com.media.module.interfaces.FocusCallback;
import com.media.module.interfaces.KeyEventsListener;
import com.media.module.model.CameraMode;
import com.media.module.model.FacingMode;
import com.media.module.model.FocusMode;
import com.media.module.utils.DimenValues;
import com.media.module.utils.SupportedSizesReflect;
import com.media.module.utils.UIGlobalDef;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.Area;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import timber.log.Timber;

/**
 * 
 * Camera Preview
 * 
 * @author zhyc 2017年4月11日下午3:30:40
 */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback, Camera.AutoFocusCallback {

	private final String TAG = "CameraPreview";
	private static final int ACCURACY = 3;
	private float preXvaule;
	private FacingMode mFacingMode = FacingMode.BACK;

	private Context mContext;
	private Camera mCamera;
	private int mintWidthPixels;
	private int mintHeightPixels;
	private boolean misPixelsSet;

	private Paint mPaint;
	private FocusMode focusMode = FocusMode.AUTO;

	private boolean mbEnableFocus;
	private boolean mbHasFocused = false;
	private float mPrevScaleFactor;
	private FocusCallback mFocusCallback;
	private KeyEventsListener mKeyEventsListener;

	private int[] measure_spec = new int[2];
	private boolean has_aspect_ratio = false;
	private double aspect_ratio = 0.0f;
	private int focus_screen_x = 0;
	private int focus_screen_y = 0;
	private int focus_success = FOCUS_DONE;
	private static final int FOCUS_WAITING = 0;
	private static final int FOCUS_SUCCESS = 1;
	private static final int FOCUS_FAILED = 2;
	private static final int FOCUS_DONE = 3;
	private static final int FOCUS_DISAPPEAR = 4;

	private void setAspectRatio(double ratio) {
		if (ratio <= 0.0)
			throw new IllegalArgumentException();
		has_aspect_ratio = true;
		if (aspect_ratio != ratio) {
			aspect_ratio = ratio;
			this.requestLayout();
		}
	}

	private boolean hasAspectRatio() {
		return has_aspect_ratio;
	}

	private double getAspectRatio() {
		return aspect_ratio;
	}

	public Parameters getParameters (){
		
		return  mCamera.getParameters();
	}
	
	
	public CameraPreview(Context context, Camera camera) {
		super(context);
		this.mContext = context;
		this.mCamera = camera;
		initHolder();
	}

	/**
	 * @param pictureSize
	 *            the pictureSize to set
	 */
	public void setPictureSize(int height, int width) {
		mintHeightPixels = height;
		mintWidthPixels = width;
	}

	/**
	 * @param misPixelsSet
	 *            the misPixelsSet to set
	 */
	public void setMisPixelsSet(boolean misPixelsSet) {
		this.misPixelsSet = misPixelsSet;
	}

	public CameraPreview(Context context, Camera camera, ImageView canvasFrame, FocusCallback focusCallback,
			KeyEventsListener keyEventsListener) {
		super(context);
		this.setWillNotDraw(false);// 必须
		this.mContext = context;
		this.mCamera = camera;
		this.mFocusCallback = focusCallback;
		this.mKeyEventsListener = keyEventsListener;

		List<String> supportedFocusModes = camera.getParameters().getSupportedFocusModes();
		mbEnableFocus = supportedFocusModes != null && supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO);
		initHolder();
	}

	private void initHolder() {
		// Install a SurfaceHolder.Callback so we get notified when the
		// underlying surface is created and destroyed.
		SurfaceHolder holder = getHolder();
		if (holder != null) {
			holder.addCallback(this);
			holder.setKeepScreenOn(true);
		}
	}

	private void initFocusDrawingTools(int width, int height) {
		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setStrokeCap(Paint.Cap.ROUND);
		final float scale = getContext().getResources().getDisplayMetrics().density;
		mPaint.setStrokeWidth((float) (0.5f * scale + 0.5f));// convert dps to
																// pixels
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		Timber.d("surfaceCreated");
		// The Surface has been created, now tell the camera where to draw the
		// preview.
		startPreview(holder);
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		Timber.d("surfaceDestroyed");
		stopPreview();
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		Timber.d("surfaceChanged(%1d, %2d)", width, height);
		// If your preview can change or rotate, take care of those events here.
		// Make sure to stop the preview before resizing or reformatting it.
		initFocusDrawingTools(width, height);
		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT,
				LayoutParams.MATCH_PARENT);
		params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
		setLayoutParams(params);
		if (holder.getSurface() == null) {
			// preview surface does not exist
			return;
		}
		stopPreview();

		startPreview(holder);
		setOnTouchListener(new CameraTouchListener());
	}

	@Override
	protected void onDraw(Canvas canvas) {
		drawFocusFrame(canvas);
	}

	@Override
	protected void onMeasure(int widthSpec, int heightSpec) {
		getMeasureSpec(measure_spec, widthSpec, heightSpec);
		super.onMeasure(measure_spec[0], measure_spec[1]);
	}

	private void startPreview(SurfaceHolder holder) {
		Timber.d("startPreview");
		try {
			mCamera.setPreviewDisplay(holder);
			Camera.Parameters parameters = mCamera.getParameters();
			if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
				mCamera.setDisplayOrientation(90);
				parameters.set("orientation", "portrait");
			} else {
				parameters.set("orientation", "landscape");
				mCamera.setDisplayOrientation(0);
			}
			if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
				parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
			}
			List<Size> supportedPictureSizes = SupportedSizesReflect.getSupportedPictureSizes(parameters);
			List<Size> supportedPreviewSizes = SupportedSizesReflect.getSupportedPreviewSizes(parameters);

			if (supportedPictureSizes != null && supportedPreviewSizes != null && supportedPictureSizes.size() > 0
					&& supportedPreviewSizes.size() > 0) {
				Size pictureSize = supportedPictureSizes.get(0);
				DisplayMetrics displayMetrics = new DisplayMetrics();
				((Activity) mContext).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
				int maxSize = displayMetrics.heightPixels;
				if (!misPixelsSet) {
					Size[] sizeArr = supportedPictureSizes.toArray(new Size[supportedPictureSizes.size()]);
					for (int i = 0; i < sizeArr.length - 1; i++) {
						for (int j = 0; j < sizeArr.length - 1; j++) {
							if (sizeArr[j].height > sizeArr[j + 1].height) {
								Size size = sizeArr[j];
								sizeArr[j] = sizeArr[j + 1];
								sizeArr[j + 1] = size;
							}
						}
					}

					// 2.x
					pictureSize = sizeArr[0];
					// 取分辨率大于720的分辨率
					for (Size size : sizeArr) {
						if (size.height > 720
								&& null != getOptimalPreviewSize(getMatchPreviewSizes(supportedPreviewSizes, size),
										displayMetrics.widthPixels, displayMetrics.heightPixels)) {
							pictureSize = size;
							break;
						}
					}

					int abs = Math.abs(pictureSize.height - maxSize);
					for (Size size : sizeArr) {
						if (abs < Math.abs(Math.max(size.height, size.width) - maxSize)
								&& pictureSize.height / pictureSize.width == 0.75) {
							pictureSize = size;
							break;
						}
						abs = Math.abs(Math.max(size.height, size.width) - maxSize);
					}
				}

				parameters.setPictureSize(pictureSize.width, pictureSize.height);
				if (mintWidthPixels > 0 && mintHeightPixels > 0) {
					pictureSize.height = mintHeightPixels;
					pictureSize.width = mintWidthPixels;
					parameters.setPictureSize(mintWidthPixels, mintHeightPixels);
				}

				Size previewSize = getOptimalPreviewSize(getMatchPreviewSizes(supportedPreviewSizes, pictureSize),
						displayMetrics.widthPixels, displayMetrics.heightPixels);

				parameters.setPreviewSize(previewSize.width, previewSize.height);
				this.setAspectRatio(((double) previewSize.width) / (double) previewSize.height);
			}

			mCamera.cancelAutoFocus();
			holder.setKeepScreenOn(true);// 屏幕常亮
			if(mFacingMode.getId() == CameraInfo.CAMERA_FACING_BACK)parameters.setJpegQuality(100); // 设置照片的质量
			mCamera.setParameters(parameters);
			mCamera.startPreview();
			mCamera.autoFocus(this);
		} catch (Exception e) {
			Timber.e(e, "Error starting camera preview: " + e.getMessage());
		}
	}

	public void setFocusMode(FocusMode focusMode) {
		clearCameraFocus();
		this.focusMode = focusMode;
		focus_success = FOCUS_DONE;
		setOnTouchListener(new CameraTouchListener());
	}

	public Camera switchFacingMode(FacingMode facingMode) {
		if (mCamera != null) {
			mCamera.release();
			mCamera = null;
		}
		mFacingMode = facingMode;
		mCamera = Camera.open(facingMode.getId());
		startPreview(getHolder());// 开始预览
		return mCamera;
	}

	private void stopPreview() {
		Timber.d("stopPreview");
		try {
			mCamera.stopPreview();
		} catch (Exception e) {
			Timber.e(e, "Error stopping camera preview: " + e.getMessage());
		}
	}

	public void getMeasureSpec(int[] spec, int widthSpec, int heightSpec) {
		if (!this.hasAspectRatio()) {
			spec[0] = widthSpec;
			spec[1] = heightSpec;
			return;
		}
		double aspect_ratio = this.getAspectRatio();

		int previewWidth = MeasureSpec.getSize(widthSpec);
		int previewHeight = MeasureSpec.getSize(heightSpec);

		// Get the padding of the border background.
		int hPadding = this.getPaddingLeft() + this.getPaddingRight();
		int vPadding = this.getPaddingTop() + this.getPaddingBottom();

		// Resize the preview frame with correct aspect ratio.
		previewWidth -= hPadding;
		previewHeight -= vPadding;

		boolean widthLonger = previewWidth > previewHeight;
		int longSide = (widthLonger ? previewWidth : previewHeight);
		int shortSide = (widthLonger ? previewHeight : previewWidth);
		if (longSide > shortSide * aspect_ratio) {
			longSide = (int) ((double) shortSide * aspect_ratio);
		} else {
			shortSide = (int) ((double) longSide / aspect_ratio);
		}
		if (widthLonger) {
			previewWidth = longSide;
			previewHeight = shortSide;
		} else {
			previewWidth = shortSide;
			previewHeight = longSide;
		}

		// Add the padding of the border.
		previewWidth += hPadding;
		previewHeight += vPadding;

		spec[0] = MeasureSpec.makeMeasureSpec(previewWidth, MeasureSpec.EXACTLY);
		spec[1] = MeasureSpec.makeMeasureSpec(previewHeight, MeasureSpec.EXACTLY);
	}

	/**
	 * 获取和图片尺寸匹配的预览尺寸
	 * 
	 * @param supportedPreviewSizes
	 * @param pictureSize
	 * @return
	 */
	private List<Size> getMatchPreviewSizes(List<Size> supportedPreviewSizes, Size pictureSize) {
		List<Size> supportSizes = new ArrayList<Camera.Size>();
		for (Size size : supportedPreviewSizes) {
			if (size.height * pictureSize.width == pictureSize.height * size.width) {
				supportSizes.add(size);
			}
		}
		return supportSizes;
	}

	private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
		final double ASPECT_TOLERANCE = 0.1;
		double targetRatio = (double) w / h;
		if (sizes == null)
			return null;

		Size optimalSize = null;
		double minDiff = Double.MAX_VALUE;

		int targetHeight = h;

		// Try to find an size match aspect ratio and size
		for (Size size : sizes) {
			double ratio = (double) size.width / size.height;
			if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
				continue;
			if (Math.abs(size.height - targetHeight) < minDiff) {
				optimalSize = size;
				minDiff = Math.abs(size.height - targetHeight);
			}
		}

		// Cannot find the one match the aspect ratio, ignore the requirement
		if (optimalSize == null) {
			minDiff = Double.MAX_VALUE;
			for (Size size : sizes) {
				if (Math.abs(size.height - targetHeight) < minDiff) {
					optimalSize = size;
					minDiff = Math.abs(size.height - targetHeight);
				}
			}
		}
		return optimalSize;
	}

	private void drawFocusFrame(Canvas canvas) {
		if (mFacingMode.getId() == CameraInfo.CAMERA_FACING_FRONT)
			return;
		mPaint.setXfermode(new PorterDuffXfermode(Mode.OVERLAY));
		final float scale = getContext().getResources().getDisplayMetrics().density;
		float min_radius = (float) (40 * scale + 0.5f); // convert dps to pixels
		float radius = min_radius;
		int size = (int) radius;

		if (focus_success == FOCUS_SUCCESS) {
			mPaint.setColor(Color.rgb(20, 231, 21));
			new DrawThread().start();
		} // Green A400
		if (focus_success == FOCUS_FAILED)
			mPaint.setColor(Color.rgb(244, 67, 54)); // Red 500
		if (focus_success == FOCUS_DISAPPEAR) {
			mPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
		}
		if (focus_success == FOCUS_WAITING || focus_success == FOCUS_DONE)
			mPaint.setColor(Color.WHITE);
		mPaint.setStyle(Paint.Style.STROKE);
		int pos_x = 0;
		int pos_y = 0;
		if (mbHasFocused) {
			pos_x = focus_screen_x;
			pos_y = focus_screen_y;
		} else {
			pos_x = canvas.getWidth() / 2;
			pos_y = canvas.getHeight() / 2;
		}
		float frac = 0.5f;
		// horizontal strokes
		canvas.drawLine(pos_x - size, pos_y - size, pos_x - frac * size, pos_y - size, mPaint);
		canvas.drawLine(pos_x + frac * size, pos_y - size, pos_x + size, pos_y - size, mPaint);
		canvas.drawLine(pos_x - size, pos_y + size, pos_x - frac * size, pos_y + size, mPaint);
		canvas.drawLine(pos_x + frac * size, pos_y + size, pos_x + size, pos_y + size, mPaint);
		// vertical strokes
		canvas.drawLine(pos_x - size, pos_y - size, pos_x - size, pos_y - frac * size, mPaint);
		canvas.drawLine(pos_x - size, pos_y + frac * size, pos_x - size, pos_y + size, mPaint);
		canvas.drawLine(pos_x + size, pos_y - size, pos_x + size, pos_y - frac * size, mPaint);
		canvas.drawLine(pos_x + size, pos_y + frac * size, pos_x + size, pos_y + size, mPaint);
		mPaint.setStyle(Paint.Style.FILL); // reset
	}

	class DrawThread extends Thread {
		@Override
		public void run() {
			try {
				Thread.sleep(1 * 1000);
				focus_success = FOCUS_DISAPPEAR;
				postInvalidate();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	};

	@SuppressWarnings("deprecation")
	private void clearCameraFocus() {
		if (mbEnableFocus) {
			mbHasFocused = false;
			focus_success = FOCUS_DONE;
			try {
				mCamera.cancelAutoFocus();
			} catch (Exception e) {
				// TODO: handle exception
				Log.e(VIEW_LOG_TAG, e.getMessage());
			}

			try {
				Camera.Parameters parameters = mCamera.getParameters();
				parameters.setFocusAreas(null);
				parameters.setMeteringAreas(null);
				mCamera.setParameters(parameters);
			} catch (Exception e) {
				Timber.e(e, "clearCameraFocus");
			}
		}
	}

	@Override
	public void onAutoFocus(boolean success, Camera camera) {
		focus_success = success ? FOCUS_SUCCESS : FOCUS_FAILED;
		postInvalidate();
		focused();
	}

	private void focused() {
		if (mFocusCallback != null) {
			mFocusCallback.onFocused(mCamera);
		}
	}

	protected void focusOnTouch(MotionEvent event) {
		clearCameraFocus();
		postInvalidate();
		mbHasFocused = false;
		focus_screen_x = (int) event.getX();
		focus_screen_y = (int) event.getY();
		Camera.Parameters parameters = mCamera.getParameters();
		ArrayList<Area> areas = getAreas(event.getX(), event.getY());
		setFocusAndMeteringArea(areas);
		parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
		mCamera.setParameters(parameters);
		mbHasFocused = true;
		focus_success = FOCUS_WAITING;
		mCamera.autoFocus(this);
	}

	private void setFocusAndMeteringArea(List<Area> areas) {
		List<Camera.Area> camera_areas = new ArrayList<Camera.Area>();
		for (Area area : areas) {
			camera_areas.add(new Camera.Area(area.rect, area.weight));
		}
		Camera.Parameters parameters = mCamera.getParameters();
		String focus_mode = parameters.getFocusMode();
		// getFocusMode() is documented as never returning null, however I've
		// had null pointer exceptions reported in Google Play
		if (parameters.getMaxNumFocusAreas() != 0 && focus_mode != null
				&& (focus_mode.equals(Camera.Parameters.FOCUS_MODE_AUTO)
						|| focus_mode.equals(Camera.Parameters.FOCUS_MODE_MACRO)
						|| focus_mode.equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)
						|| focus_mode.equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO))) {
			parameters.setFocusAreas(camera_areas);

			// also set metering areas
			if (parameters.getMaxNumMeteringAreas() == 0) {
				Log.d(TAG, "metering areas not supported");
			} else {
				parameters.setMeteringAreas(camera_areas);
			}

			mCamera.setParameters(parameters);
		} else if (parameters.getMaxNumMeteringAreas() != 0) {
			parameters.setMeteringAreas(camera_areas);

			mCamera.setParameters(parameters);
		}
	}

	private ArrayList<Area> getAreas(float x, float y) {
		float[] coords = { x, y };
		float focus_x = coords[0];
		float focus_y = coords[1];

		int focus_size = 50;
		Log.d(TAG, "x, y: " + x + ", " + y);
		Log.d(TAG, "focus x, y: " + focus_x + ", " + focus_y);
		Rect rect = new Rect();
		rect.left = (int) focus_x - focus_size;
		rect.right = (int) focus_x + focus_size;
		rect.top = (int) focus_y - focus_size;
		rect.bottom = (int) focus_y + focus_size;
		if (rect.left < -1000) {
			rect.left = -1000;
			rect.right = rect.left + 2 * focus_size;
		} else if (rect.right > 1000) {
			rect.right = 1000;
			rect.left = rect.right - 2 * focus_size;
		}
		if (rect.top < -1000) {
			rect.top = -1000;
			rect.bottom = rect.top + 2 * focus_size;
		} else if (rect.bottom > 1000) {
			rect.bottom = 1000;
			rect.top = rect.bottom - 2 * focus_size;
		}

		ArrayList<Area> areas = new ArrayList<Area>();
		areas.add(new Area(rect, 1000));
		return areas;
	}

	private void scale(float scaleFactor) {
		scaleFactor = BigDecimal.valueOf(scaleFactor).setScale(ACCURACY, BigDecimal.ROUND_HALF_UP).floatValue();
		if (Float.compare(scaleFactor, 1.0f) == 0 || Float.compare(scaleFactor, mPrevScaleFactor) == 0) {
			return;
		}
		if (scaleFactor > 1f) {
			mKeyEventsListener.zoomIn();
		}
		if (scaleFactor < 1f) {
			mKeyEventsListener.zoomOut();
		}
		mPrevScaleFactor = scaleFactor;
	}

	private class CameraTouchListener implements OnTouchListener {

		private ScaleGestureDetector mScaleDetector = new ScaleGestureDetector(mContext, new ScaleListener());
		private GestureDetector mTapDetector = new GestureDetector(mContext, new TapListener());

		@Override
		public boolean onTouch(View v, MotionEvent event) {
			clearCameraFocus();
			if (event.getPointerCount() > 1) {
				mScaleDetector.onTouchEvent(event);
			}
			if (mbEnableFocus && focusMode == FocusMode.TOUCH) {
				mTapDetector.onTouchEvent(event);
			}
			if(!misPixelsSet||mFacingMode.getId() == CameraInfo.CAMERA_FACING_FRONT)return true;
			switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN:
				preXvaule = event.getX();
				break;
			case MotionEvent.ACTION_MOVE:
				float motionValue = event.getX() - preXvaule;
				if (motionValue > 0 || Math.abs(motionValue) < DimenValues.dipToPx(mContext, 40)) {
					return true;
				}
				preXvaule = event.getX();
				SettingFragment fragment = new SettingFragment(mContext);
				((Activity) mContext).getFragmentManager().beginTransaction().setCustomAnimations(R.animator.fragment_slide_right_in, R.animator.fragment_slide_left_out)
						.replace(UIGlobalDef.VIEW_ID.FRAME_LAYOUT, fragment).addToBackStack(null)
                        .commit();
				break;
			default:
				break;
			}
			return true;
		}

		private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

			@Override
			public boolean onScale(ScaleGestureDetector detector) {
				scale(detector.getScaleFactor());
				return true;
			}

		}

		private class TapListener extends GestureDetector.SimpleOnGestureListener {

			@Override
			public boolean onSingleTapConfirmed(MotionEvent event) {
				if (mFacingMode.getId() == CameraInfo.CAMERA_FACING_BACK)
					focusOnTouch(event);
				return true;
			}

		}

	}

}
